From 0a465f829af47a8f74aceb8121a8326b866af73a Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 26 Jun 2025 07:48:38 -0400 Subject: [PATCH] Import jsonwrt code from util-linux We've had a longstanding need to emit JSON to be friendlier to shell scripting tools. I hesitated for (way too long) on which JSON libraries to use, but actually since we don't need to *parse* JSON, we only need to omit it, the problem domain is super simple. util-linux has simple code for this https://github.com/util-linux/util-linux/commit/b649ff92d2d91864e85a5bba4c89d52236817d9e So this commit imports it, but it will need a little massaging to build. Signed-off-by: Colin Walters --- Makefile-otutil.am | 2 + src/libotutil/ul-jsonwrt.c | 266 +++++++++++++++++++++++++++++++++++++ src/libotutil/ul-jsonwrt.h | 59 ++++++++ 3 files changed, 327 insertions(+) create mode 100644 src/libotutil/ul-jsonwrt.c create mode 100644 src/libotutil/ul-jsonwrt.h diff --git a/Makefile-otutil.am b/Makefile-otutil.am index 291a2e80..86c1f6f9 100644 --- a/Makefile-otutil.am +++ b/Makefile-otutil.am @@ -41,6 +41,8 @@ libotutil_la_SOURCES = \ src/libotutil/otutil.h \ src/libotutil/ot-tool-util.c \ src/libotutil/ot-tool-util.h \ + src/libotutil/ul-jsonwrt.h \ + src/libotutil/ul-jsonwrt.c \ $(NULL) if USE_GPGME diff --git a/src/libotutil/ul-jsonwrt.c b/src/libotutil/ul-jsonwrt.c new file mode 100644 index 00000000..365d845c --- /dev/null +++ b/src/libotutil/ul-jsonwrt.c @@ -0,0 +1,266 @@ +/* + * JSON output formatting functions. + * + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Written by Karel Zak + */ +#include +#include +#include +#include + +#include "c.h" +#include "jsonwrt.h" + +/* + * Requirements enumerated via testing (V8, Firefox, IE11): + * + * var charsToEscape = []; + * for (var i = 0; i < 65535; i += 1) { + * try { + * JSON.parse('{"sample": "' + String.fromCodePoint(i) + '"}'); + * } catch (e) { + * charsToEscape.push(i); + * } + * } + */ +static void fputs_quoted_case_json(const char *data, FILE *out, int dir, size_t size) +{ + const char *p; + + fputc('"', out); + for (p = data; p && *p && (!size || p < data + size); p++) { + + const unsigned int c = (unsigned int) *p; + + /* From http://www.json.org + * + * The double-quote and backslashes would break out a string or + * init an escape sequence if not escaped. + * + * Note that single-quotes and forward slashes, while they're + * in the JSON spec, don't break double-quoted strings. + */ + if (c == '"' || c == '\\') { + fputc('\\', out); + fputc(c, out); + continue; + } + + /* All non-control characters OK; do the case swap as required. */ + if (c >= 0x20) { + /* + * Don't use locale sensitive ctype.h functions for regular + * ASCII chars, because for example with Turkish locale + * (aka LANG=tr_TR.UTF-8) toupper('I') returns 'I'. + */ + if (c <= 127) + fputc(dir == 1 ? c_toupper(c) : + dir == -1 ? c_tolower(c) : *p, out); + else + fputc(dir == 1 ? toupper(c) : + dir == -1 ? tolower(c) : *p, out); + continue; + } + + /* In addition, all chars under ' ' break Node's/V8/Chrome's, and + * Firefox's JSON.parse function + */ + switch (c) { + /* Handle short-hand cases to reduce output size. C + * has most of the same stuff here, so if there's an + * "Escape for C" function somewhere in the STL, we + * should probably be using it. + */ + case '\b': + fputs("\\b", out); + break; + case '\t': + fputs("\\t", out); + break; + case '\n': + fputs("\\n", out); + break; + case '\f': + fputs("\\f", out); + break; + case '\r': + fputs("\\r", out); + break; + default: + /* Other assorted control characters */ + fprintf(out, "\\u00%02x", c); + break; + } + } + fputc('"', out); +} + +#define fputs_quoted_json(_d, _o) fputs_quoted_case_json(_d, _o, 0, 0) +#define fputs_quoted_json_upper(_d, _o) fputs_quoted_case_json(_d, _o, 1, 0) +#define fputs_quoted_json_lower(_d, _o) fputs_quoted_case_json(_d, _o, -1, 0) + +void ul_jsonwrt_init(struct ul_jsonwrt *fmt, FILE *out, int indent) +{ + fmt->out = out; + fmt->indent = indent; + fmt->after_close = 0; +} + +int ul_jsonwrt_is_ready(struct ul_jsonwrt *fmt) +{ + return fmt->out == NULL ? 0 : 1; +} + +void ul_jsonwrt_indent(struct ul_jsonwrt *fmt) +{ + int i; + + for (i = 0; i < fmt->indent; i++) + fputs(" ", fmt->out); +} + +static void print_name(struct ul_jsonwrt *fmt, const char *name) +{ + if (name) { + if (fmt->after_close) + fputs(",\n", fmt->out); + ul_jsonwrt_indent(fmt); + fputs_quoted_json_lower(name, fmt->out); + } else { + if (fmt->after_close) + fputs(",", fmt->out); + else + ul_jsonwrt_indent(fmt); + } +} + +void ul_jsonwrt_open(struct ul_jsonwrt *fmt, const char *name, int type) +{ + print_name(fmt, name); + + switch (type) { + case UL_JSON_OBJECT: + fputs(name ? ": {\n" : "{\n", fmt->out); + fmt->indent++; + break; + case UL_JSON_ARRAY: + fputs(name ? ": [\n" : "[\n", fmt->out); + fmt->indent++; + break; + case UL_JSON_VALUE: + fputs(name ? ": " : " ", fmt->out); + break; + } + fmt->after_close = 0; +} + +void ul_jsonwrt_empty(struct ul_jsonwrt *fmt, const char *name, int type) +{ + print_name(fmt, name); + + switch (type) { + case UL_JSON_OBJECT: + fputs(name ? ": {}" : "{}", fmt->out); + break; + case UL_JSON_ARRAY: + fputs(name ? ": []" : "[]", fmt->out); + break; + case UL_JSON_VALUE: + fputs(name ? ": null" : "null", fmt->out); + break; + } + + fmt->after_close = 1; +} + +void ul_jsonwrt_close(struct ul_jsonwrt *fmt, int type) +{ + assert(fmt->indent > 0); + + switch (type) { + case UL_JSON_OBJECT: + fmt->indent--; + fputc('\n', fmt->out); + ul_jsonwrt_indent(fmt); + fputs("}", fmt->out); + if (fmt->indent == 0) + fputs("\n", fmt->out); + break; + case UL_JSON_ARRAY: + fmt->indent--; + fputc('\n', fmt->out); + ul_jsonwrt_indent(fmt); + fputs("]", fmt->out); + break; + case UL_JSON_VALUE: + break; + } + + fmt->after_close = 1; +} + + +void ul_jsonwrt_flush(struct ul_jsonwrt *fmt) +{ + fflush(fmt->out); +} + +void ul_jsonwrt_value_raw(struct ul_jsonwrt *fmt, + const char *name, const char *data) +{ + ul_jsonwrt_value_open(fmt, name); + if (data && *data) + fputs(data, fmt->out); + else + fputs("null", fmt->out); + ul_jsonwrt_value_close(fmt); +} + +void ul_jsonwrt_value_s(struct ul_jsonwrt *fmt, + const char *name, const char *data) +{ + ul_jsonwrt_value_open(fmt, name); + if (data && *data) + fputs_quoted_json(data, fmt->out); + else + fputs("null", fmt->out); + ul_jsonwrt_value_close(fmt); +} + +void ul_jsonwrt_value_s_sized(struct ul_jsonwrt *fmt, + const char *name, const char *data, size_t size) +{ + ul_jsonwrt_value_open(fmt, name); + if (data && *data) + fputs_quoted_case_json(data, fmt->out, 0, size); + else + fputs("null", fmt->out); + ul_jsonwrt_value_close(fmt); +} + +void ul_jsonwrt_value_u64(struct ul_jsonwrt *fmt, + const char *name, uint64_t data) +{ + ul_jsonwrt_value_open(fmt, name); + fprintf(fmt->out, "%"PRIu64, data); + ul_jsonwrt_value_close(fmt); +} + +void ul_jsonwrt_value_double(struct ul_jsonwrt *fmt, + const char *name, long double data) +{ + ul_jsonwrt_value_open(fmt, name); + fprintf(fmt->out, "%Lg", data); + ul_jsonwrt_value_close(fmt); +} + +void ul_jsonwrt_value_boolean(struct ul_jsonwrt *fmt, + const char *name, int data) +{ + ul_jsonwrt_value_open(fmt, name); + fputs(data ? "true" : "false", fmt->out); + ul_jsonwrt_value_close(fmt); +} diff --git a/src/libotutil/ul-jsonwrt.h b/src/libotutil/ul-jsonwrt.h new file mode 100644 index 00000000..f156fdd4 --- /dev/null +++ b/src/libotutil/ul-jsonwrt.h @@ -0,0 +1,59 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + */ +#ifndef UTIL_LINUX_JSONWRT_H +#define UTIL_LINUX_JSONWRT_H + +enum { + UL_JSON_OBJECT, + UL_JSON_ARRAY, + UL_JSON_VALUE +}; + +struct ul_jsonwrt { + FILE *out; + int indent; + + unsigned int after_close :1; +}; + +void ul_jsonwrt_init(struct ul_jsonwrt *fmt, FILE *out, int indent); +int ul_jsonwrt_is_ready(struct ul_jsonwrt *fmt); +void ul_jsonwrt_indent(struct ul_jsonwrt *fmt); +void ul_jsonwrt_open(struct ul_jsonwrt *fmt, const char *name, int type); +void ul_jsonwrt_close(struct ul_jsonwrt *fmt, int type); +void ul_jsonwrt_empty(struct ul_jsonwrt *fmt, const char *name, int type); +void ul_jsonwrt_flush(struct ul_jsonwrt *fmt); + +#define ul_jsonwrt_root_open(_f) ul_jsonwrt_open(_f, NULL, UL_JSON_OBJECT) +#define ul_jsonwrt_root_close(_f) ul_jsonwrt_close(_f, UL_JSON_OBJECT) + +#define ul_jsonwrt_array_open(_f, _n) ul_jsonwrt_open(_f, _n, UL_JSON_ARRAY) +#define ul_jsonwrt_array_close(_f) ul_jsonwrt_close(_f, UL_JSON_ARRAY) +#define ul_jsonwrt_array_empty(_f, _n) ul_jsonwrt_empty(_f, _n, UL_JSON_ARRAY) + +#define ul_jsonwrt_object_open(_f, _n) ul_jsonwrt_open(_f, _n, UL_JSON_OBJECT) +#define ul_jsonwrt_object_close(_f) ul_jsonwrt_close(_f, UL_JSON_OBJECT) +#define ul_jsonwrt_object_empty(_f, _n) ul_jsonwrt_empty(_f, _n, UL_JSON_OBJECT) + +#define ul_jsonwrt_value_open(_f, _n) ul_jsonwrt_open(_f, _n, UL_JSON_VALUE) +#define ul_jsonwrt_value_close(_f) ul_jsonwrt_close(_f, UL_JSON_VALUE) + +void ul_jsonwrt_value_raw(struct ul_jsonwrt *fmt, + const char *name, const char *data); +void ul_jsonwrt_value_s(struct ul_jsonwrt *fmt, + const char *name, const char *data); +void ul_jsonwrt_value_s_sized(struct ul_jsonwrt *fmt, + const char *name, const char *data, size_t size); +void ul_jsonwrt_value_u64(struct ul_jsonwrt *fmt, + const char *name, uint64_t data); +void ul_jsonwrt_value_double(struct ul_jsonwrt *fmt, + const char *name, long double data); +void ul_jsonwrt_value_boolean(struct ul_jsonwrt *fmt, + const char *name, int data); + +#define ul_jsonwrt_value_null(_f, _n) ul_jsonwrt_empty(_f, _n, UL_JSON_VALUE) + +#endif /* UTIL_LINUX_JSONWRT_H */ + -- 2.30.2